package com.lechi.web.server.handle;

import com.alibaba.fastjson2.JSON;
import com.lechi.iot.netty.utils.BytesToHexUtil;
import com.lechi.web.common.utils.DateUtils;
import com.lechi.web.constants.Constants;
import com.lechi.web.constants.TcpMessageConstants;
import com.lechi.web.dto.IotDeviceCmdParseDto;
import com.lechi.web.framework.util.GuavaCacheUtil;
import com.lechi.web.framework.websocket.WebSocketServer;
import com.lechi.web.framework.websocket.WebSocketUtil;
import com.lechi.web.system.domain.IotMonitorDataBo;
import com.lechi.web.util.RecordFileUtil;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ArrayUtils;
import org.springframework.beans.BeanUtils;

import java.text.DecimalFormat;
import java.util.*;
import java.util.concurrent.CompletableFuture;

/**
 * 自定义解码器，将字节流解码为 Java 对象。通常用于接收到数据后进行解码。
 * ，不是所有的handler都可以添加这个注解的，被修饰的handler必须是无状态的，或者说，线程安全的。例如，如果解码器继承自ByteToMessageDecoder，就不能添加该注解。因为ByteToMessageDecoder类里含有成员变量。
 */
@Slf4j
public class ModBusTcpMessageHandler extends ByteToMessageDecoder implements BaseWeatherInboundHandle {

    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {
        String hostName = getHostName(ctx);
        in.markReaderIndex();
        byte[] heart = new byte[in.readableBytes()];
        in.readBytes(heart);
//        log.error("ModBusTcp 设备地址：{}, 接收到的原始报文未黏包处理:{}", hostName, Optional.of(heart).map(BytesToHexUtil::bytesToHexString).get());
        String inputDeviceSN = new String(heart).trim();
        if (GuavaCacheUtil.getBiDeviceSnAndIPMap().containsKey(inputDeviceSN)) {
            GuavaCacheUtil.getBiDeviceSnAndIPMap().forcePut(inputDeviceSN, hostName);
            log.error("ModBusTcp 监听接收到设备注册地址：" + hostName + "，注册序列号: " + inputDeviceSN);
            return;
        }
        in.resetReaderIndex();
        List<byte[]> frames = parseFrames(in);
        if (frames.isEmpty()) {
            in.resetReaderIndex(); // 没有完整报文时回退指针
            log.error("数据不足，等待下次解析，当前可读字节: {}", in.readableBytes());
            return;
        }
        for (byte[] frame : frames) {
            CompletableFuture.runAsync(() -> {
                processFrames(frame, hostName);
            });
        }
    }

    private void processFrames(byte[] frame, String hostName) {
        String decodeStr = Optional.of(frame).map(BytesToHexUtil::bytesToHexString).get();
        log.error("ModBusTcp 设备地址：{}, 开始解析报文:{}", hostName, decodeStr);
        String deviceSNAndAddr = GuavaCacheUtil.getBiDeviceSnAndIPMap().inverse().get(hostName) + "_" + BytesToHexUtil.bytesToHexString(Arrays.copyOf(frame,1));
        var cmdMsgList = GuavaCacheUtil.getCmdParseTableMap().get(deviceSNAndAddr);
        if (cmdMsgList == null || cmdMsgList.isEmpty()) {
            getKeysByValue(hostName).forEach(deviceId -> WebSocketServer.sendInfo(deviceId, JSON.toJSONString(Map.of("record", decodeStr, "time", DateUtils.getTime()))));
            log.error("ModBusTcp 报文中从站地址拼接设备序列号：{} 从缓存中没有获取到，该报文不解析!", deviceSNAndAddr);
            return;
        }
        List<IotMonitorDataBo> tcpDeviceDataList = new ArrayList<>();
        for (Object object : cmdMsgList) {
            IotDeviceCmdParseDto iotDeviceCmdParseDto = (IotDeviceCmdParseDto) object;
            IotMonitorDataBo iotMonitorPageData = new IotMonitorDataBo();
            BeanUtils.copyProperties(iotDeviceCmdParseDto, iotMonitorPageData);
            try {
                byte[] changeValue = buildDataByBitSort(iotDeviceCmdParseDto, frame);
                String sensorValue = BytesToHexUtil.bytesToHexString(changeValue);
                String v = "";
                Integer digit = Optional.ofNullable(iotDeviceCmdParseDto.getSensorDigit()).orElse(0);
                double intFactor = Optional.ofNullable(iotDeviceCmdParseDto.getIntFactor()).orElse(1d);
                if (Constants.INTEGER_DATA_FORMAT_32.equals(iotDeviceCmdParseDto.getDataFormat())) {
                    v = hexTo32Int(sensorValue, intFactor, digit);
                } else if (Constants.INTEGER_DATA_FORMAT_16.equals(iotDeviceCmdParseDto.getDataFormat())) {
                        v = hexTo16Int(sensorValue, intFactor, digit);
                } else if (Constants.FLOAT_FORMAT_32.equals(iotDeviceCmdParseDto.getDataFormat())){
                    v = hexTo32Float(sensorValue, intFactor, digit);
                } else if (Constants.FLOAT_FORMAT_16.equals(iotDeviceCmdParseDto.getDataFormat())){
                    v = hexTo16Float(sensorValue, intFactor, digit);
                }
                iotMonitorPageData.setValue(Float.parseFloat(v)).setDoubleValue(v).setStringValue(v).setDateTime(new Date());
                insertData(iotMonitorPageData);
                tcpDeviceDataList.add(iotMonitorPageData);
            } catch (Exception e) {
                log.error("ModBusTcp 报文详情：{}, deviceSNAndAddr:{}, 解析异常", decodeStr, deviceSNAndAddr, e);
                continue;
            }
            alarmRuleChain(iotMonitorPageData);
            WebSocketUtil.sendToMonitorPage(iotMonitorPageData);
            GuavaCacheUtil.getMonitorDataMap().put(iotMonitorPageData.getDeviceId(), iotMonitorPageData.getSensorAliasId(), iotMonitorPageData);
            GuavaCacheUtil.getDeviceIdAndIPMap().put(iotMonitorPageData.getDeviceId(), hostName);
            GuavaCacheUtil.getDeviceLastDated().put(String.valueOf(iotMonitorPageData.getDeviceId()), new Date());
        }
        WebSocketUtil.sendToDevicePage(Constants.MODBUS_RTU, tcpDeviceDataList, decodeStr);
        RecordFileUtil.producerRecord(tcpDeviceDataList, decodeStr, "");
    }

    private List<byte[]> parseFrames(ByteBuf buffer) {
        List<byte[]> frames = new ArrayList<>();
        int searchStart = buffer.readerIndex(); // 从当前读指针开始
        while (buffer.writerIndex() - searchStart >= 5) { // 确保至少有最小帧长度的字节可读
            int frameLength = findFrameLength(buffer, searchStart);        // 检查帧长度过长，或者帧不完整
            if (frameLength <= 0 || (searchStart + frameLength) > buffer.writerIndex()) { // 验证当前窗口位置+理论帧长度是否超出缓冲区实际数据范围
                searchStart++;
                continue;
            }
            byte[] frame = new byte[frameLength];             // 提取完整帧并验证
            buffer.getBytes(searchStart, frame); // 从当前读指针位置获取完整帧数据
            if (validateCRC(frame)) {
                frames.add(frame);
                searchStart += frameLength; // 成功解析后移动指针
            } else {
                searchStart++; // CRC校验失败逐步滑动
            }
        }
        buffer.readerIndex(searchStart);  // 关键修改：设置新的读指针位置
        return frames;
    }

    private int findFrameLength(ByteBuf buffer, int startIndex) {
        byte slaveAddress = buffer.getByte(startIndex);
        byte functionCode = buffer.getByte(startIndex + 1);
        if (slaveAddress < 0 || slaveAddress > 247 || functionCode < 1 || functionCode > 255) {
            return -1;
        }
        int dataLength = buffer.getByte(startIndex + 2) & 0xFF;
        int frameLength = 5 + dataLength;
        if (frameLength < 5 || frameLength > 256) {
            return -1;
        }
        return frameLength;
    }

    // 验证CRC校验码
    private boolean validateCRC(byte[] frame) {
        int length = frame.length;
        if (length < 4) return false;
        int calculatedCRC = calculateCRC(frame, 0, length - 2);
        int receivedCRC = (frame[length - 2] & 0xFF) | ((frame[length - 1] & 0xFF) << 8);
        return calculatedCRC == receivedCRC;
    }

    private int calculateCRC(byte[] data, int offset, int length) {
        int crc = 0xFFFF;
        for (int i = offset; i < offset + length; i++) {
            crc ^= (data[i] & 0xFF);
            for (int j = 0; j < 8; j++) {
                if ((crc & 0x0001) != 0) {
                    crc >>= 1;
                    crc ^= 0xA001;
                } else {
                    crc >>= 1;
                }
            }
        }
        return crc;
    }

    private static byte[] buildDataByBitSort(IotDeviceCmdParseDto iotDeviceCmdParseDto, byte[] sourceData) {
        byte[] targetData = ArrayUtils.subarray(sourceData, iotDeviceCmdParseDto.getBeginIndex() + 3, iotDeviceCmdParseDto.getEndIndex() + 3);
        switch (iotDeviceCmdParseDto.getBitSort()) {
            case TcpMessageConstants.BA_FORMAT:
                byte[] bytesBA = new byte[2];
                bytesBA[0] = targetData[1];
                bytesBA[1] = targetData[0];
                return bytesBA;
            case TcpMessageConstants.CDAB_FORMAT:
                byte[] bytesCDAB = new byte[4];
                System.arraycopy(targetData, 2, bytesCDAB, 0, 2);
                System.arraycopy(targetData, 0, bytesCDAB, 2, 2);
                return bytesCDAB;
            default:
                return targetData;
        }
    }

    /**
     * 将十六进制字符串转换为32位浮点数
     */
    private static String hexTo32Float(String hexString, double intFactor, Integer digit) {
        int intBits = (int) Long.parseLong(hexString, 16);
        float v = Float.intBitsToFloat(intBits) * (float) intFactor;
        return formatValue(v, digit);
    }

    /**
     * 将十六进制字符串转换为16位浮点数
     */
    private static String hexTo16Float(String hexString, double intFactor, Integer digit) {
        short shortValue = Short.parseShort(hexString, 16);
        float v = shortValue * (float) intFactor;
        return formatValue(v, digit);
    }

    /**
     * 将十六进制字符串转换为32位整型
     */
    private static String hexTo32Int(String hexString, double intFactor, Integer digit) {
        long longValue = Long.parseLong(hexString, 16);
        long v = (long) (longValue * intFactor);
        return formatValue(v, digit);
    }

    /**
     * 将十六进制字符串转换为16位整型
     */
    private static String hexTo16Int(String hexString, double intFactor, Integer digit) {
        short shortValue = Short.parseShort(hexString, 16);
        float v = shortValue * (float) intFactor;
        return formatValue(v, digit);
    }

    private static String formatValue(float value, Integer digit) {
        return new DecimalFormat(Optional.ofNullable(digit)
                .filter(n -> n > 0)
                .map(n -> "0." + "0".repeat(n))
                .orElse("0"))
                .format(value);
    }
}
